/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.zz.framework.core.utils

import android.os.Handler
import android.os.Message
import android.os.SystemClock

/**
 * Schedule a countdown until a time in the future, with
 * regular notifications on intervals along the way.
 *
 * Example of showing a 30 second countdown in a text field:
 *
 * <pre class="prettyprint">
 * new CountDownTimer(30000, 1000) {
 *
 * public void onTick(long millisUntilFinished) {
 * mTextField.setText("seconds remaining: " + millisUntilFinished / 1000);
 * }
 *
 * public void onFinish() {
 * mTextField.setText("done!");
 * }
 * }.start();
</pre> *
 *
 * The calls to [.onTick] are synchronized to this object so that
 * one call to [.onTick] won't ever occur before the previous
 * callback is complete.  This is only relevant when the implementation of
 * [.onTick] takes an amount of time to execute that is significant
 * compared to the countdown interval.
 */
abstract class ZCountDownTimer(
    /**
     * Millis since epoch when alarm should stop.
     */
    protected var millisInFuture: Long,
    /**
     * The interval in millis that the user receives callbacks
     */
    private val countDownInterval: Long
) {
    private var stopTimeInFuture: Long = 0

    /**
     * boolean representing if the timer was cancelled
     */
    private var cancelled = false

    /**
     * Cancel the countdown.
     */
    @Synchronized
    fun cancel() {
        cancelled = true
        handler.removeMessages(MSG)
    }

    /**
     * Start the countdown.
     */
    @Synchronized
    fun start(): ZCountDownTimer {
        cancelled = false
        if (millisInFuture <= 0) {
            onFinish()
            return this
        }
        stopTimeInFuture = SystemClock.elapsedRealtime() + millisInFuture
        handler.sendMessage(handler.obtainMessage(MSG))
        return this
    }

    @Synchronized
    fun restart(): ZCountDownTimer {
        cancel()
        start()
        return this
    }

    /**
     * Callback fired on regular interval.
     * @param millisUntilFinished The amount of time until finished.
     */
    abstract fun onTick(millisUntilFinished: Long)

    /**
     * Callback fired when the time is up.
     */
    abstract fun onFinish()

    private companion object {
        private const val MSG = 1
    }

    // handles counting down
    private val handler = object : Handler() {
        override fun handleMessage(msg: Message) {
            synchronized(this@ZCountDownTimer) {
                if (cancelled) {
                    return
                }

                val millisLeft = stopTimeInFuture - SystemClock.elapsedRealtime()

                if (millisLeft <= 0) {
                    onFinish()
                } else {
                    val lastTickStart = SystemClock.elapsedRealtime()
                    onTick(millisLeft)

                    // take into account user's onTick taking time to execute
                    val lastTickDuration = SystemClock.elapsedRealtime() - lastTickStart
                    var delay: Long

                    if (millisLeft < countDownInterval) {
                        // just delay until done
                        delay = millisLeft - lastTickDuration

                        // special case: user's onTick took more than interval to
                        // complete, trigger onFinish without delay
                        if (delay < 0) delay = 0
                    } else {
                        delay = countDownInterval - lastTickDuration
                        // special case: user's onTick took more than interval to
                        // complete, skip to next interval
                        while (delay < 0) delay += countDownInterval
                    }
                    sendMessageDelayed(obtainMessage(MSG), delay)
                }
            }
        }
    }
}
